package reticulado;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import soot.Local;
import soot.Unit;
import soot.toolkits.scalar.FlowSet;

public class PointsToGraph implements FlowSet {

    private Set<InsideNode> inodes;
    private Set<LoadNode> lnodes;
    private Set<ParameterNode> pnodes;
    private Estado estado;
    private Set<EjeR> ejesR;
    private Set<EjeW> ejesW;
    private Set<Unit> problemas;

    public PointsToGraph() {
        inodes = new TreeSet<InsideNode>();
        lnodes = new TreeSet<LoadNode>();
        pnodes = new TreeSet<ParameterNode>();
        ejesR = new HashSet<EjeR>();
        ejesW = new HashSet<EjeW>();
        problemas = new HashSet<Unit>();
        estado = new Estado();
    }

    public PointsToGraph(Estado estado, Set<InsideNode> inodes, Set<LoadNode> lnodes, Set<ParameterNode> pnodes, Set<EjeR> ejesR, Set<EjeW> ejesW, Set<Unit> problemas) {
        this.estado = estado;
        this.inodes = inodes;
        this.lnodes = lnodes;
        this.pnodes = pnodes;
        this.ejesR = ejesR;
        this.ejesW = ejesW;
        this.problemas = problemas;
    }

    public Set<AbstractNode> alcanzados(Local v) {
        if (estado.get(v) == null) {
            return new TreeSet<AbstractNode>();
        } else {
            return estado.get(v);
        }
    }

    public Set<EjeR> getEjesR() {
        return ejesR;
    }

    public Set<EjeW> getEjesW() {
        return ejesW;
    }

    public Set<AbstractNode> alcanzados(Local v, String campo) {
        Set<AbstractNode> nodosAlcanzados = new TreeSet<AbstractNode>();
        if (estado.containsKey(v)) {
            for (AbstractNode nodo : alcanzados(v)) {
                nodosAlcanzados.addAll(alcanzados(nodo, campo));
            }
        }
        return nodosAlcanzados;
    }

    public Set<AbstractNode> alcanzados(AbstractNode n, String campo) {
        Set<AbstractNode> nodosAlcanzados = new TreeSet<AbstractNode>();

        for (Eje eje : ejesW) {
            if (eje.getDesde().equals(n) && eje.getLabel().equals(campo)) {
                nodosAlcanzados.add(eje.getHasta());
            }
        }
        for (Eje eje : ejesR) {
            if (eje.getDesde().equals(n) && eje.getLabel().equals(campo)) {
                nodosAlcanzados.add(eje.getHasta());
            }
        }
        return nodosAlcanzados;
    }

    // v1 == v2
    public Boolean mayAlias(Local v1, Local v2) {
        if (definida(v1) && definida(v2)) {
            Set<AbstractNode> inter = intersection(alcanzados(v1), alcanzados(v2));
            inter.remove(NullNode.getInstance());
            return !inter.isEmpty();
        } else {
            return Boolean.FALSE;
        }
    }
    // v1.f=v2?
    public Boolean mayAlias(Local v1, String campo, Local v2) {
        if (definida(v1) && definida(v2)) {
            return !intersection(alcanzados(v1, campo), alcanzados(v2)).isEmpty();
        } else {
            return Boolean.FALSE;
        }
    }
    // v1.f=v2.g?
    public Boolean mayAlias(Local v1, String campof, Local v2,String campog) {
        if (definida(v1) && definida(v2)) {
            return !intersection(alcanzados(v1, campof), alcanzados(v2,campog)).isEmpty();
        } else {
            return Boolean.FALSE;
        }
    }

    public enum RespuestaNulidad {
        ES_NULL,
        PUEDE_SER_NULL,
        NO_ES_NULL;
    }

    public RespuestaNulidad isNull(Local v1) {
        if (estado.containsKey(v1)) {
            Set<AbstractNode> alcanzados = alcanzados(v1);
            return analizarNulidad(alcanzados);
        } else {
            return RespuestaNulidad.ES_NULL;
        }
    }

    public RespuestaNulidad isNull(Local v1, String campo) {
        if (estado.containsKey(v1)) {
            Set<AbstractNode> alcanzados = alcanzados(v1, campo);
            return analizarNulidad(alcanzados);
        } else {
            return RespuestaNulidad.ES_NULL;
        }
    }

    public RespuestaNulidad analizarNulidad(Set<AbstractNode> alcanzados) {
        if (alcanzados == null || alcanzados.isEmpty()) {
            return RespuestaNulidad.ES_NULL;
        } else {
            if (alcanzados.contains(NullNode.getInstance())) {
                if (alcanzados.size() == 1) {
                    return RespuestaNulidad.ES_NULL;
                } else {
                    return RespuestaNulidad.PUEDE_SER_NULL;
                }
            } else {
                return RespuestaNulidad.NO_ES_NULL;
            }
        }
    }

    public Set<AbstractNode> nodoCargado(AbstractNode nodo, String campo) {
        Set<AbstractNode> cargados = new HashSet<AbstractNode>();
        for (Eje eje : ejesR) {
            if (eje.getDesde().equals(nodo) && eje.getLabel().equals(campo)) {
                cargados.add(eje.getHasta());
            }
        }
        for (Eje eje : ejesW) {
            if (eje.getDesde().equals(nodo) && eje.getLabel().equals(campo)) {
                cargados.add(eje.getHasta());
            }
        }

        return cargados;
    }

    private Set<AbstractNode> intersection(Set<AbstractNode> set1, Set<AbstractNode> set2) {
        if (set1 != null && set2 != null) {
            Set<AbstractNode> intersection = new HashSet<AbstractNode>(set1);
            intersection.retainAll(set2);
            return intersection;
        } else {
            return new TreeSet<AbstractNode>();
        }
    }

    public Set<Local> variables() {
        return estado.keySet();
    }

    public Boolean definida(Local v) {
        return estado.containsKey(v);
    }

    public void definir(Local v, Set<AbstractNode> nodos) {
        estado.put(v, nodos);
    }

    public void definir(Local v, AbstractNode nodo) {
        if (definida(v)) {
            estado.get(v).add(nodo);
        } else {
            Set nodos = new TreeSet();
            nodos.add(nodo);
            definir(v, nodos);
        }
    }

    // El siguiente metodo esta sobrecargado para InsideNode, LoadNode, ParameterNode, EjeR y EjeW
    // Devuelve el nodo que agrego (si no estaba) o sino devuelve el que ya estaba
    public InsideNode agregar(InsideNode nodo) {
        InsideNode res = nodo;
        if (inodes.contains(nodo)) {
            for (InsideNode n : inodes) {
                if (n.equals(nodo)) {
                    res = n;
                }
            }
        } else {
            inodes.add(nodo);
        }
        return res;
    }

    public LoadNode agregar(LoadNode nodo) {
        LoadNode res = nodo;
        if (lnodes.contains(nodo)) {
            for (LoadNode n : lnodes) {
                if (n.equals(nodo)) {
                    res = n;
                }
            }
        } else {
            lnodes.add(nodo);
        }
        return res;
    }

    public ParameterNode agregar(ParameterNode nodo) {
        ParameterNode res = nodo;
        if (pnodes.contains(nodo)) {
            for (ParameterNode n : pnodes) {
                if (n.equals(nodo)) {
                    res = n;
                }
            }
        } else {
            pnodes.add(nodo);
        }
        return res;
    }

    public void agregar(EjeR nuevoEje) {
        ejesR.add(nuevoEje);
    }

    public void agregar(EjeW nuevoEje) {
        ejesW.add(nuevoEje);
    }

    public void agregarProblema(Unit sentencia) {
        problemas.add(sentencia);
    }

    public Set<Eje> getEjes() {
        Set<Eje> ret = new HashSet<Eje>();
        ret.addAll(ejesR);
        ret.addAll(ejesW);
        return ret;
    }

    public void setEjes(Set<EjeR> eR, Set<EjeW> eW) {
        ejesR = eR;
        ejesW = eW;
    }

    public Set<AbstractNode> getNodes() {
        Set<AbstractNode> ret = new HashSet<AbstractNode>();
        ret.addAll(inodes);
        ret.addAll(lnodes);
        ret.addAll(pnodes);
        return ret;
    }

    public void setInsideNodes(Set<InsideNode> inodes) {
        this.inodes = inodes;
    }

    public void setLoadNodes(Set<LoadNode> lnodes) {
        this.lnodes = lnodes;
    }

    public void setParameterNodes(Set<ParameterNode> pnodes) {
        this.pnodes = pnodes;
    }

    public Set<Unit> getProblemas() {
        return problemas;
    }

    public void setProblemas(Set<Unit> problemas) {
        this.problemas = problemas;
    }

    public Estado getEstado() {
        return estado;
    }

    public void setEstado(Estado estado) {
        this.estado = estado;
    }

    @Override
    public FlowSet clone() {
        PointsToGraph res = new PointsToGraph();
        res.copy(this);
        return res;
    }

    @Override
    public Object emptySet() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void copy(FlowSet arg0) {
        PointsToGraph ptg = (PointsToGraph) arg0;
        this.ejesR = new HashSet<EjeR>(ptg.ejesR);
        this.ejesW = new HashSet<EjeW>(ptg.ejesW);
        this.inodes = new TreeSet<InsideNode>(ptg.inodes);
        this.lnodes = new TreeSet<LoadNode>(ptg.lnodes);
        this.pnodes = new TreeSet<ParameterNode>(ptg.pnodes);
        this.problemas = new HashSet<Unit>(ptg.problemas);
        this.estado.copy(ptg.getEstado());
    }

    @Override
    public void clear() {
        this.ejesR.clear();
        this.ejesW.clear();
        this.inodes.clear();
        this.lnodes.clear();
        this.pnodes.clear();
        this.problemas.clear();
        this.estado.clear();
    }

    @Override
    public void union(FlowSet ptg) {
        PointsToGraph ptgAuxiliar = (PointsToGraph) ptg;
        this.ejesR.addAll(ptgAuxiliar.ejesR);
        this.ejesW.addAll(ptgAuxiliar.ejesW);
        this.inodes.addAll(ptgAuxiliar.inodes);
        this.lnodes.addAll(ptgAuxiliar.lnodes);
        this.pnodes.addAll(ptgAuxiliar.pnodes);
        this.problemas.addAll(ptgAuxiliar.getProblemas());
        this.estado.union(ptgAuxiliar.getEstado());
    }

    @Override
    public void union(FlowSet fs2, FlowSet res) {
        res.clear();
        res.union(fs2);
        res.union(this);
    }

    @Override
    public void intersection(FlowSet arg0) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void intersection(FlowSet arg0, FlowSet arg1) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void difference(FlowSet arg0) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void difference(FlowSet arg0, FlowSet arg1) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public boolean isEmpty() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int size() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void add(Object arg0) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void add(Object arg0, FlowSet arg1) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void remove(Object arg0) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void remove(Object arg0, FlowSet arg1) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public boolean contains(Object arg0) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Iterator iterator() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public List toList() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public String toString() {
        String res = "";
        res += "EjesR: " + ejesR.toString() + "\n";
        res += "EjesW: " + ejesW.toString() + "\n";
        res += "InsideNodes: " + inodes.toString() + "\n";
        res += "LoadNodes: " + lnodes.toString() + "\n";
        res += "ParameterNodes: " + pnodes.toString() + "\n";
        res += "Estado: " + estado.toString() + "\n";
        res += "Problemas: " + problemas.toString() + "\n";
        return res;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final PointsToGraph other = (PointsToGraph) obj;
        if (this.inodes != other.inodes && (this.inodes == null || !this.inodes.equals(other.inodes))) {
            return false;
        }
        if (this.lnodes != other.lnodes && (this.lnodes == null || !this.lnodes.equals(other.lnodes))) {
            return false;
        }
        if (this.pnodes != other.pnodes && (this.pnodes == null || !this.pnodes.equals(other.pnodes))) {
            return false;
        }
        if (this.estado != other.estado && (this.estado == null || !this.estado.equals(other.estado))) {
            return false;
        }
        if (this.ejesR != other.ejesR && (this.ejesR == null || !this.ejesR.equals(other.ejesR))) {
            return false;
        }
        if (this.ejesW != other.ejesW && (this.ejesW == null || !this.ejesW.equals(other.ejesW))) {
            return false;
        }
        if (this.problemas != other.problemas && (this.problemas == null || !this.problemas.equals(other.problemas))) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 97 * hash + (this.inodes != null ? this.inodes.hashCode() : 0);
        hash = 97 * hash + (this.lnodes != null ? this.lnodes.hashCode() : 0);
        hash = 97 * hash + (this.pnodes != null ? this.pnodes.hashCode() : 0);
        hash = 97 * hash + (this.estado != null ? this.estado.hashCode() : 0);
        hash = 97 * hash + (this.ejesR != null ? this.ejesR.hashCode() : 0);
        hash = 97 * hash + (this.ejesW != null ? this.ejesW.hashCode() : 0);
        hash = 97 * hash + (this.problemas != null ? this.problemas.hashCode() : 0);
        return hash;
    }
}
